package com.cea.papyrus.ocl.interpreter;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.ocl.examples.interpreter.OCLExamplePlugin;
import org.eclipse.emf.ocl.examples.interpreter.console.DelegatingPackageRegistry;
import org.eclipse.emf.ocl.examples.interpreter.console.IOCLFactory;
import org.eclipse.emf.ocl.examples.interpreter.console.ModelingLevel;
import org.eclipse.emf.ocl.examples.interpreter.console.text.ColorManager;
import org.eclipse.emf.ocl.examples.interpreter.console.text.OCLDocument;
import org.eclipse.emf.ocl.examples.interpreter.console.text.OCLSourceViewer;
import org.eclipse.emf.ocl.examples.interpreter.internal.l10n.OCLInterpreterMessages;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.ocl.OCL;
import org.eclipse.ocl.Query;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.helper.ConstraintKind;
import org.eclipse.ocl.helper.OCLHelper;
import org.eclipse.ocl.types.TupleType;
import org.eclipse.ocl.uml.UMLEnvironmentFactory;
import org.eclipse.ocl.uml.util.OCLUMLUtil;
import org.eclipse.ocl.util.Tuple;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Constraint;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Namespace;
import org.eclipse.uml2.uml.OpaqueExpression;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.UMLFactory;

public class OCLEditorComposite implements ISectionComposite {

	/** Default modeling level */
	private final static ModelingLevel defaultModelingLevel = ModelingLevel.M1;

	/** Viewer that displays the result of the constraint evaluation */
	private ITextViewer output;

	/** Viewer that displays the body of the constraint */
	private OCLSourceViewer input;

	/** Document that holds the body of the constraint */
	private OCLDocument document;

	/** color manager for the source viewer */
	private ColorManager colorManager;

	/** current constraint */
	private Constraint constraint;

	/** context of the constraint */
	private Element context;

	/** Factory to create ocl rules for uml metamodel */ 
	private IOCLFactory<Object> oclFactory = new UMLOCLFactory();

	/** modeling level (M1 or M2) */
	// private ModelingLevel modelingLevel = ModelingLevel.M1;

	/** Widget to choose the modeling level */
	private CCombo levelCCombo;

	/** Listener for the level selection widget */
	private SelectionListener levelListener;

	/** Combo to choose the language (Natural, OCL, etc.) */
	private CCombo languageCCombo;

	/** Listener for the language selection widget */
	private LanguageListener languageListener;

	/** Adapter factory (TODO should be taken from the MultiDiagramEditor */ 
	private static final AdapterFactory reflectiveAdapterFactory = new ReflectiveItemProviderAdapterFactory();

	/** Key to retrieve the eAnnotation that has the constraint modeling level */
	public static final String PAPYRUS_CONSTRAINT_MODELING_LEVEL_KEY = "ModelingLevel";	

	/** Button to launch evaluation of the constraint */ 
	private Button evaluateButton;

	/** Listener for the evaluate button */
	private MouseListener evaluateListener;

	/** Button to save the actual constraint body */
	private Button saveButton;

	/** Listener for the save button */
	private SaveListener saveListener;

	/** Label provider for tuples */
	private IItemLabelProvider tupleTypeLabelProvider = new TupleTypeLabelProvider();

	/** Input key listener for the body source viewer */
	private InputKeyListener inputKeyListener;

	/** Indicates if the console is enable or not */
	private Boolean isConsoleEnabled = false;

	/** main composite of this complex composite */
	protected Composite composite;

	/** current language edited */
	private String currentLanguage;

	/** current modeling level */
	private ModelingLevel currentModelingLevel;


	/**
	 * Creates a new Editor Composite, with a new {@link UMLOCLFactory}.
	 */
	public OCLEditorComposite() {
		oclFactory = new UMLOCLFactory();
		document =  new OCLDocument();            
		document.setModelingLevel(defaultModelingLevel); // default value that should be overriden
		document.setOCLFactory(oclFactory);

		// initialize listeners
		levelListener = new LevelListener();
		languageListener = new LanguageListener();
		saveListener = new SaveListener();
		inputKeyListener = new InputKeyListener();
		evaluateListener = new EvaluateListener();

		// initialize color manager
		colorManager = new ColorManager();

		// default modeling level
		currentModelingLevel = defaultModelingLevel;
	}

	/** @{inheritDoc} */
	public Composite createContent(Composite parent, TabbedPropertySheetWidgetFactory factory) {
		composite = factory.createFlatFormComposite(parent);

		// level   MO/M1   |    Language  OCL/Natural   |   Save
		CLabel levelLabel = factory.createCLabel(composite, "Level", SWT.FLAT);
		FormData data = new FormData();
		data.top  = new FormAttachment(0, 0);
		data.left = new FormAttachment(0, 0);
		levelLabel.setLayoutData(data);

		// CCombo for level
		levelCCombo = factory.createCCombo(composite, SWT.BORDER);
		levelCCombo.add(ModelingLevel.M1.toString());
		levelCCombo.add(ModelingLevel.M2.toString());
		levelCCombo.setEditable(false);
		levelCCombo.addSelectionListener(levelListener);	
		data = new FormData();
		data.top  = new FormAttachment(0, 0);
		data.left = new FormAttachment(levelLabel, 0);
		levelCCombo.setLayoutData(data);

		// Label for language
		CLabel languageLabel = factory.createCLabel(composite, "Language", SWT.FLAT);
		languageLabel.setText("Language");
		data = new FormData();
		data.top  = new FormAttachment(0, 0);
		data.left = new FormAttachment(levelCCombo, 0);
		languageLabel.setLayoutData(data);

		// CCombo for language
		languageCCombo = factory.createCCombo(composite, SWT.BORDER);
		languageCCombo.setEditable(false);
		languageCCombo.add("OCL");
		languageCCombo.add("Natural");
		languageCCombo.addSelectionListener(languageListener);
		data = new FormData();
		data.top  = new FormAttachment(0, 0);
		data.left = new FormAttachment(languageLabel, 0);
		languageCCombo.setLayoutData(data);

		saveButton = factory.createButton(composite, "Save", SWT.FLAT | SWT.PUSH);
		saveButton.setVisible(true);						
		saveButton.addMouseListener(saveListener);
		data = new FormData();
		data.top  = new FormAttachment(0, 0);
		data.left = new FormAttachment(languageCCombo, 0);
		saveButton.setLayoutData(data);

		///////////////////////////////////////////////////////////////////////
		// input viewer and console output  
		// creates the console after the input, to have the more possible space
		// create the evaluate button at the end of the output,
		evaluateButton = factory.createButton(composite, "Evaluate", SWT.FLAT | SWT.PUSH);
		evaluateButton.setVisible(true);
		evaluateButton.addMouseListener(evaluateListener);
		data = new FormData();
		data.right  = new FormAttachment(100, 0);
		data.bottom  = new FormAttachment(100, 0);
		evaluateButton.setLayoutData(data);

		output = new TextViewer(composite, SWT.BORDER | SWT.MULTI);
		output.getTextWidget().setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
		output.setDocument(new Document());
		output.setEditable(false);
		data = new FormData();
		data.bottom  = new FormAttachment(100, 0);
		data.left = new FormAttachment(0, 0);
		data.right = new FormAttachment(evaluateButton, 0);
		output.getTextWidget().setLayoutData(data);

		input = new OCLSourceViewer(composite, colorManager, SWT.BORDER | SWT.MULTI);
		input.getTextWidget().addKeyListener(inputKeyListener);
		data = new FormData();
		data.top  = new FormAttachment(levelLabel, 0);
		data.bottom  = new FormAttachment(output. getTextWidget(), 0);
		data.left = new FormAttachment(0, 0);
		data.right = new FormAttachment(100, 0);
		input.getTextWidget().setLayoutData(data);

		return composite;
	}

	/**
	 * Sets the layout data to the main composite of this complex element.
	 * @param data the new LayoutData
	 */
	public void setLayoutData(Object data) {
		composite.setLayoutData(data);
	}

	/**
	 * Returns the main composite that constitutes this complex composite 
	 * @return the main composite that constitutes this complex composite 
	 */
	public Composite getMainComposite() {
		return composite;
	}

	/** @{inheritDoc} */
	public void dispose() {
		if ((saveButton!=null) && !(saveButton.isDisposed())){
			saveButton.removeMouseListener(saveListener);
		}
		if ((evaluateButton != null) && !(evaluateButton.isDisposed())){
			evaluateButton.removeMouseListener(evaluateListener);
		}
		if ((levelCCombo != null) && !(levelCCombo.isDisposed())){
			levelCCombo.removeSelectionListener(levelListener);
		}
		if ((languageCCombo != null) && !(languageCCombo.isDisposed())){
			languageCCombo.removeSelectionListener(languageListener);
		}
		if ((input != null) && (input.getTextWidget()!=null) && !(input.getTextWidget().isDisposed())){
			input.getTextWidget().removeKeyListener(inputKeyListener);
		}
		// disposing main composite will dispose sub composites
		composite.dispose();
	}

	/** @{inheritDoc} */
	public void refresh() {
		// if constraint is null, everything is disabled, and text is erased from the input and output
		if(constraint == null) {
			// clear the text in the input
			if(input.getDocument()!=null) {
				input.getDocument().set("--- select a constraint to be edited ---");	
			}
			setConsoleEnabled(false);
		} else {
			// constraint is not null, update the different items. Console will be enable
			setConsoleEnabled(true);
			// retrieve specification from the constraint. Erase previous if not an OpaqueExpression
			OpaqueExpression specification;
			if(constraint.getSpecification()==null || !(constraint.getSpecification() instanceof OpaqueExpression)) {
				specification = (OpaqueExpression)constraint.createSpecification("specification", null, UMLFactory.eINSTANCE.getUMLPackage().getOpaqueExpression());
				specification.getLanguages().add("OCL");
				specification.getBodies().add("");
				currentLanguage = "OCL";
			} else {
				specification = (OpaqueExpression)constraint.getSpecification();
			}

			// refresh button language selection. Should select a language among several 
			if("OCL".equals(currentLanguage)) {
				languageCCombo.select(0);
				evaluateButton.setEnabled(true);
				output.getDocument().set("");
			} else {
				languageCCombo.select(1);
				evaluateButton.setEnabled(false);
				if(output!=null && output.getDocument()!=null) {
					output.getDocument().set("Only OCL constraints can be evaluated");	
				}
			}

			// refresh modeling level (M1 = 0)
			if(ModelingLevel.M1.equals(currentModelingLevel)) {
				levelCCombo.select(0);

				// evaluate button should be checked again : only for classifier and operation
				if(evaluateButton.isEnabled()) { // if it was already available
					if( !( context instanceof Classifier || context instanceof Operation) ) {
						evaluateButton.setEnabled(false);
						if(output!=null && output.getDocument()!=null) {
							output.getDocument().set("M1 constraints can be evaluated only in the context of classifiers or operations");	
						}
					}
				}
			} else {
				levelCCombo.select(1);
			}

			// refresh Document
			document.set(com.cea.papyrus.umlutils.OpaqueExpression.getBodyForLanguage(specification, currentLanguage));
			document.setModelingLevel(currentModelingLevel);
			input.setDocument(document);

			saveButton.setEnabled(isSaveButtonEnable());
		}
	}

	/**
	 * Indicates if the save button should be enable.
	 * @return <code>true</code> if save is activated
	 */
	private boolean isSaveButtonEnable() {
		// checks current body / current language / current modeling level
		// if they are equals to those saved in the constraints, save is enabled
		boolean isEnabled = false;
		
		if(currentModelingLevel != getModelingLevel(constraint)) {
			isEnabled = true;
		}
		
		OpaqueExpression specification =(OpaqueExpression)constraint.getSpecification(); 
		
		// save is enable if the language does not already exists in the constraint
		if(!specification.getLanguages().contains(currentLanguage) ) {
			isEnabled = true;
		}
		
		// get the body for the current language
		if(!document.get().equals(com.cea.papyrus.umlutils.OpaqueExpression.getBodyForLanguage(specification, currentLanguage))) {
			isEnabled = true;
		}
		
		return isEnabled;
	}

	/**
	 * Prints the specified <code>object</code> to the output viewer.  The
	 * object is converted to a string using the best matching EMF label
	 * provider adapter if it is an {@link EObject}; otherwise, just use
	 * {@link String#valueOf(java.lang.Object)} on it.  If the
	 * <code>object</code> is a collection or an array, then we print each
	 * element on a separate line.
	 * 
	 * @param object the object or collection to print
	 * @param color the color to print the <code>object</code> with
	 * @param bold whether to display it in bold text
	 */
	private void print(Object object, Color color, boolean bold) {
		Collection<?> toPrint;

		if (object == null) {
			toPrint = Collections.EMPTY_SET;
		} else if (object instanceof Collection) {
			toPrint = (Collection<?>) object;
		} else if (object.getClass().isArray()) {
			toPrint = Arrays.asList((Object[]) object);
		} else {
			toPrint = Collections.singleton(object);
		}

		for (Iterator<?> iter = toPrint.iterator(); iter.hasNext();) {
			append(toString(iter.next()), color, bold);
		}

		scrollText();
	}
	/**
	 * Ensures that the last text printed to the output viewer is shown.
	 */
	private void scrollText() {
		output.revealRange(getDocument().getLength(), 0);
	}
	/**
	 * Appends the specified text to the output viewer.
	 * 
	 * @param text the text to append
	 * @param color the color to print the text with
	 * @param bold whether to print the text bold
	 */
	private void append(String text, Color color, boolean bold) {

		IDocument doc = getDocument();
		try {
			int offset = doc.getLength();
			int length = text.length();

			text = '\n'+text;

			if (offset > 0) {
				doc.replace(offset, 0, text);
			} else {
				doc.set(text);
			}

			StyleRange style = new StyleRange();
			style.start = offset;
			style.length = length;
			style.foreground = color;

			if (bold) {
				style.fontStyle = SWT.BOLD;
			}

			output.getTextWidget().setStyleRange(style);
		} catch (BadLocationException e) {
			IStatus status = new Status(
					IStatus.ERROR,
					OCLExamplePlugin.getPluginId(),
					1,
					OCLInterpreterMessages.console_outputExc,
					e);

			OCLExamplePlugin.getDefault().getLog().log(status);
		}
	}


	/**
	 * Enables the console (i.e. enables all widgets)
	 * @param enabled <code>true</code> to enables the composite
	 */
	public void setConsoleEnabled(Boolean enabled){
		isConsoleEnabled = enabled;
		input.getTextWidget().setEnabled(enabled);		
		input.getTextWidget().setEditable(enabled);	
		languageCCombo.setEnabled(enabled);
		levelCCombo.setEnabled(enabled);
		saveButton.setEnabled(enabled);
		evaluateButton.setEnabled(enabled);			
	}

	/**
	 * Returns <code>true</code> if the console is enabled
	 * @return <code>true</code> if the console is enabled
	 */
	public boolean isConsoleEnabled(){
		return isConsoleEnabled;
	}

	/**
	 * Sets the text in the document and then pass the new document to the input.
	 * @param text
	 */
	public void setText(String text){
		document.set(text);
		input.setDocument(document);
	}

	/**
	 * Sets the language of the constraint
	 * @param language the new Language for the constraint
	 */
	public void setLanguage(String language){
		if ("OCL".equals(language)){
			languageCCombo.select(0);
			evaluateButton.setEnabled(true);
		} else {				
			languageCCombo.select(1);
			evaluateButton.setEnabled(false);
		}
	}

	/**
	 * Updates the current selected context
	 * @param element the selected context
	 */
	public void setContext(Namespace namespace) {
		context = namespace;
		document.setOCLContext((EObject) context);
	}

	/**
	 * Converts a single object to a string, according to the rules described
	 * for the {@link #print(Object, Color, boolean)} method.
	 * @param object the object to print (not a collection type)
	 * @return the string form of the <code>object</code>
	 * @see #print(Object, Color, boolean)
	 */
	String toString(Object object) {
		if (object instanceof EObject) {
			EObject eObject = (EObject) object;

			IItemLabelProvider labeler =
				(IItemLabelProvider) EcoreUtil.getRegisteredAdapter(
						eObject,
						IItemLabelProvider.class);

			if (labeler == null) {
				if (eObject.eClass() instanceof TupleType) {
					labeler = tupleTypeLabelProvider;
				} else {
					labeler = (IItemLabelProvider) reflectiveAdapterFactory.adapt(
							eObject,
							IItemLabelProvider.class);
				}
			}

			if (labeler != null) {
				return labeler.getText(object);
			}
		}

		return String.valueOf(object);
	}

	/**
	 * Returns the modeling level as given by the eAnnotation associated to the constraint
	 * @param constraint the constraint to edit
	 * @return the modeling value of the constraint, or the default value if no eAnnotation
	 * had the modeling level
	 */
	public ModelingLevel getModelingLevel(Constraint constraint) {
		Iterator<EAnnotation> it = constraint.getEAnnotations().iterator();
		while(it.hasNext()) {
			EAnnotation annotation = it.next();
			if(annotation.getDetails().containsKey(PAPYRUS_CONSTRAINT_MODELING_LEVEL_KEY)) {
				return ModelingLevel.valueOf(annotation.getDetails().get(PAPYRUS_CONSTRAINT_MODELING_LEVEL_KEY));
			}
		}
		return defaultModelingLevel; // default value
	}

	/**
	 * Sets the ModelingLevel for the specified constraint.<p>
	 * modeling level is registered in an eAnnotation of the constraint.
	 * @param constraint the constraint to be modified
	 * @param modelingLevel the new modeling level
	 */
	public void setModelingLevel(Constraint constraint, ModelingLevel modelingLevel) {
		EAnnotation annotation = null;
		Iterator<EAnnotation> it = constraint.getEAnnotations().iterator();
		while(it.hasNext()) {
			// saves the first one by default in annotation, in case no annotation is found
			EAnnotation currentAnnotation = it.next();
			if(currentAnnotation.getDetails().containsKey(PAPYRUS_CONSTRAINT_MODELING_LEVEL_KEY)) {
				annotation = currentAnnotation;
				break;
			}
		}
		if(annotation == null) {
			// it was not found in the set of constraints, creates a new entry in the details list of the first eAnnotation
			if(constraint.getEAnnotations().size()==0) {
				// creates a new eAnnotation
				annotation = constraint.createEAnnotation(org.eclipse.uml2.uml.UMLPackage.eNS_URI);
			} else {
				annotation = constraint.getEAnnotations().get(0);
			}
		}
		annotation.getDetails().put(PAPYRUS_CONSTRAINT_MODELING_LEVEL_KEY, modelingLevel.toString());
	}

	/**
	 * Updates the currently edited constraint
	 * @param constraint the new constraint to edit
	 */
	public void setConstraint(Constraint constraint){
		this.constraint = constraint;

		if(constraint!=null) {
			// re init the current Modeling level and the current language (default one or first one in the list)
			currentModelingLevel = getModelingLevel(constraint);

			// get possible languages (those registered in preferences and those in the languages specification)
			currentLanguage = getDefaultEditingLanguage();
		} else {
			// gives default values to these element to avoid null pointers exception
			currentModelingLevel = defaultModelingLevel;
			currentLanguage = "OCL";
		}


	}

	/**
	 * Returns the default language to edit as a new constraint is edited
	 * @return the default language to edit
	 */
	protected String getDefaultEditingLanguage() {
		String language = "OCL";
		if(constraint.getSpecification() instanceof OpaqueExpression) {
			List<String> languages = ((OpaqueExpression)constraint.getSpecification()).getLanguages();
			// returns the first one if exists
			if(languages.size()>0) {
				return languages.get(0);
			}
		}
		// returns default language
		return language;
	}

	/**
	 * Returns the current language of the constraint
	 * @return the language selected in the Combo
	 */
	public String getLanguage(){
		return languageCCombo.getText();
	}

	/**
	 * Returns the current document.
	 * @return the text in the current text
	 */
	public String getText(){
		return document.get();
	}

	/**
	 * Obtains the document in the output viewer.
	 * @return the output document
	 */
	private IDocument getDocument() {
		return output.getDocument();
	}

	/**
	 * Evaluates an OCL expression using the OCL Interpreter's {@link OCLHelper}
	 * API.
	 * @param expression an OCL expression
	 * @return <code>true</code> on successful evaluation; <code>false</code>
	 *    if the expression failed to parse or evaluate
	 */
	boolean evaluate(String expression) {
		boolean result = true;

		if (context == null) {
			result = false;
			OCLExamplePlugin.getDefault().getLog().log(new Status(
					IStatus.ERROR,
					OCLExamplePlugin.getPluginId(),
					OCLInterpreterMessages.console_noContext));
		} else {
			// create an OCL helper to do our parsing and evaluating
			OCL<?, Object, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> ocl = oclFactory.createOCL();
			OCLHelper<Object, ?, ?, ?> helper = ocl.createOCLHelper();

			// set our helper's context classifier to parse against it
			ConstraintKind kind = currentModelingLevel.setContext(helper, context, oclFactory);

			try {
				IDocument doc = getDocument();
				Color outputDefault = colorManager.getColor(ColorManager.DEFAULT);
				Color outputResults = colorManager.getColor(ColorManager.OUTPUT_RESULTS);

				if (doc.getLength() > 0) {
					// separate previous output by a blank line
					append("", outputDefault, false); //$NON-NLS-1$
				}

				// print(OCLInterpreterMessages.console_evaluating, outputDefault, true);
				// print(expression, outputDefault, false);
				// print(OCLInterpreterMessages.console_results, outputDefault, true);

				switch (currentModelingLevel) {
				case M2:
					OCLExpression<Object> parsed = helper.createQuery(expression);

					// evaluate the query
					Query<Object, ?, ?> query = ocl.createQuery(parsed);
					print(query.evaluate(context), outputResults, false);
					break;
				case M1:
					helper.createConstraint(kind, expression);

					// just report a successful parse
					print(OCLInterpreterMessages.console_parsed,
							outputResults, false);
					break;
				}

			} catch (Exception e) {
				result = false;
				OCLExamplePlugin.getDefault().getLog().log(new Status(
						IStatus.ERROR,
						OCLExamplePlugin.getPluginId(),
						e.getLocalizedMessage(),
						e));
				print(e.getLocalizedMessage(), colorManager.getColor(ColorManager.OUTPUT_ERROR), false);
			}
		}

		return result;
	}



	///////////////////////////////////////////////////////////////////////////
	// Inner Classes : Listener, etc....
	///////////////////////////////////////////////////////////////////////////
	/**
	 * listener to save to save the current body constraint.
	 */
	private class SaveListener implements MouseListener {

		/** @{inheritDoc} */
		public void mouseDoubleClick(MouseEvent e) {
			// Do nothing
		}

		/** @{inheritDoc} */
		public void mouseDown(MouseEvent e) {
			// Do nothing
		}

		/** @{inheritDoc} */
		public void mouseUp(MouseEvent e) {	
			OpaqueExpression specification;
			if ((constraint.getSpecification() == null) || !(constraint.getSpecification() instanceof OpaqueExpression)) {
				specification = (OpaqueExpression)constraint.createSpecification("specification", null, org.eclipse.uml2.uml.UMLPackage.eINSTANCE.getOpaqueExpression());					
			} else {										
				specification = (OpaqueExpression) constraint.getSpecification();					
			}
			// specification to be modified by the save action
			com.cea.papyrus.umlutils.OpaqueExpression.setBodyForLanguage(specification, currentLanguage, document.get());

			// save the modeling level
			setModelingLevel(constraint, currentModelingLevel);
		}

	}

	/** 
	 * listener to listen the language Combo action 
	 */
	private class LanguageListener implements SelectionListener {			
		/** @{inheritDoc} */
		public void widgetSelected(SelectionEvent e) {
			// sets the new language and refresh the editor
			currentLanguage = languageCCombo.getText();
			refresh();
		}

		/** @{inheritDoc} */
		public void widgetDefaultSelected(SelectionEvent e) {
		}
	}

	/**
	 * Listener for the level Combo
	 */
	private class LevelListener implements SelectionListener{

		/** @{inheritDoc} */
		public void widgetSelected(SelectionEvent e) {
			if (ModelingLevel.M1.toString().equals(levelCCombo.getText())){
				currentModelingLevel = ModelingLevel.M1;
			} else if (ModelingLevel.M2.toString().equals(levelCCombo.getText())){
				currentModelingLevel = ModelingLevel.M2;
			} else {
				// always sets a default value...
				currentModelingLevel = defaultModelingLevel;
			}
			// update the save button
			saveButton.setEnabled(true);
			refresh();
		}

		/** @{inheritDoc} */
		public void widgetDefaultSelected(SelectionEvent e) {
			// Do nothing
		}

	}

	/** 
	 * listener to listen the evaluate button action 
	 */
	private class EvaluateListener implements MouseListener {

		/** @{inheritDoc} */
		public void mouseDoubleClick(MouseEvent e) {
			// Do nothing
		}

		/** @{inheritDoc} */
		public void mouseDown(MouseEvent e) {
			// Do nothing
		}

		/** @{inheritDoc} */
		public void mouseUp(MouseEvent e) {
			BusyIndicator.showWhile(Display.getCurrent(), new Runnable() {
				/** @{inheritDoc} */
				public void run() {
					String text = document.get();
					evaluate(text);	
				}
			});
		}
	}

	/**
	 * Basic Implementation of the {@link IOCLFactory} for uml metamodel.
	 */
	private class UMLOCLFactory implements IOCLFactory<Object> {

		/** @{inheritDoc} */
		@SuppressWarnings("unchecked")
		public OCL<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> createOCL() {
			return OCL.newInstance(
					new UMLEnvironmentFactory(
							new DelegatingPackageRegistry(
									context.eResource().getResourceSet().getPackageRegistry(),
									EPackage.Registry.INSTANCE),
									context.eResource().getResourceSet()));
		}

		/** @{inheritDoc} */
		@SuppressWarnings("unchecked")
		public OCL<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> createOCL(Resource res) {
			return OCL.newInstance(
					new UMLEnvironmentFactory(
							new DelegatingPackageRegistry(
									context.eResource().getResourceSet().getPackageRegistry(),
									EPackage.Registry.INSTANCE),
									context.eResource().getResourceSet()),
									res);
		}

		/** @{inheritDoc} */
		public Object getContextClassifier(EObject object) {
			return OCLUMLUtil.getClassifier(context.eClass(),
					context.eResource().getResourceSet());
		}

		/** @{inheritDoc} */
		public String getName(Object modelElement) {
			return ((NamedElement) modelElement).getName();
		}
	}

	/**
	 * Label provider for tuple types
	 */
	public class TupleTypeLabelProvider implements IItemLabelProvider {

		/** @{inheritDoc} */
		public Object getImage(Object object) {
			return null;
		}

		/** @{inheritDoc} */
		public String getText(Object object) {
			@SuppressWarnings("unchecked")
			Tuple<?, Object> tuple = (Tuple<?, Object>) object;
			TupleType<?, ?> tupleType = tuple.getTupleType();

			StringBuffer result = new StringBuffer();
			result.append("Tuple{");//$NON-NLS-1$

			for (Iterator<?> iter = tupleType.oclProperties().iterator();
			iter.hasNext();) {

				Object next = iter.next();

				result.append(oclFactory.getName(next));
				result.append(" = "); //$NON-NLS-1$
				result.append(OCLEditorComposite.this.toString(tuple.getValue(next)));

				if (iter.hasNext()) {
					result.append(", "); //$NON-NLS-1$
				}
			}

			result.append('}');

			return result.toString();
		}
	}

	/**
	 * A key listener that listens for the Space key to trigger completion
	 */
	private class InputKeyListener implements KeyListener {			

		/** @{inheritDoc}*/
		public void keyPressed(KeyEvent e) {
			if(saveButton!=null && !saveButton.isDisposed()) {
				saveButton.setEnabled(true);
			}
		}

		/** @{inheritDoc} */
		public void keyReleased(KeyEvent e) {
			switch (e.keyCode) {
			case ' ':
				if (((e.stateMask & SWT.CTRL) == SWT.CTRL) && (context != null) && (languageCCombo.getText().equals("OCL"))) {
					input.getContentAssistant().showPossibleCompletions();
				}
			}
		}
	}

}
